﻿using Microsoft.Extensions.Options;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Volo.Abp;
using Volo.Abp.DependencyInjection;

namespace Xms.Abp.Core;

public class DistributedSemaphore : IDistributedSemaphore, ITransientDependency
{
    protected IDistributedSemaphoreProvider DistributedSemaphoreProvider { get; }
    protected DistributedSemaphoreOptions Options { get; }
    public DistributedSemaphore(
        IEnumerable<IDistributedSemaphoreProvider> distributedSemaphoreProviders,
        IOptions<DistributedSemaphoreOptions> optionsAccessor)
    {
        Options = optionsAccessor.Value;
        var providerName = Options.ProviderName ?? "Default";
        DistributedSemaphoreProvider = distributedSemaphoreProviders.First(r => r.Name == providerName);
    }
    public IDisposable Lock(int releaseCount = 1, int millisecondsTimeout = 0)
    {
        while (!CheckSemaphore(millisecondsTimeout) && DistributedSemaphoreProvider.Name != "Default")
        {
            Thread.Sleep(Options.WaitCheckInterval);
        }
        return new DisposeAction(() => Release(releaseCount));
    }

    public IDisposable Lock(TimeSpan timeout, int releaseCount = 1)
    {
        while (!CheckSemaphore(timeout) && DistributedSemaphoreProvider.Name != "Default")
        {
            Thread.Sleep(Options.WaitCheckInterval);
        }
        return new DisposeAction(() => Release(releaseCount));
    }

    public async Task<IDisposable> LockAsync(int releaseCount = 1, int millisecondsTimeout = 0, CancellationToken cancellationToken = default)
    {
        while (!await CheckSemaphoreAsync(millisecondsTimeout, cancellationToken) && DistributedSemaphoreProvider.Name != "Default")
        {
            await Task.Delay(Options.WaitCheckInterval);
        }
        return new DisposeAction(async () => await ReleaseAsync(releaseCount, cancellationToken));
    }

    public async Task<IDisposable> LockAsync(TimeSpan timeout, int releaseCount = 1, CancellationToken cancellationToken = default)
    {
        while (!await CheckSemaphoreAsync(timeout, cancellationToken) && DistributedSemaphoreProvider.Name != "Default")
        {
            await Task.Delay(Options.WaitCheckInterval);
        }
        return new DisposeAction(async () => await ReleaseAsync(releaseCount, cancellationToken));
    }

    public bool Release(int releaseCount = 1)
    {
        return DistributedSemaphoreProvider.Release(SemaphoreName, releaseCount);
    }

    public async Task<bool> ReleaseAsync(int releaseCount = 1, CancellationToken cancellationToken = default)
    {
        return await DistributedSemaphoreProvider.ReleaseAsync(SemaphoreName, releaseCount, cancellationToken);
    }

    public bool CheckSemaphore(int millisecondsTimeout = 0)
    {
        return CheckSemaphore(TimeSpan.FromMilliseconds(millisecondsTimeout));
    }

    public bool CheckSemaphore(TimeSpan timeout)
    {
        return DistributedSemaphoreProvider.CheckSemaphore(SemaphoreName, timeout, InitialCount);
    }

    public async Task<bool> CheckSemaphoreAsync(int millisecondsTimeout = 0, CancellationToken cancellationToken = default)
    {
        return await CheckSemaphoreAsync(TimeSpan.FromMilliseconds(millisecondsTimeout), cancellationToken);
    }

    public async Task<bool> CheckSemaphoreAsync(TimeSpan timeout, CancellationToken cancellationToken = default)
    {
        return await DistributedSemaphoreProvider.CheckSemaphoreAsync(SemaphoreName, timeout, InitialCount, cancellationToken);
    }
    protected string SemaphoreName { get; set; }
    protected int InitialCount { get; set; }
    public virtual void Initalization(string semaphoreName, int initialCount = 1, string tenantId = null)
    {
        SemaphoreName = semaphoreName;
        InitialCount = initialCount;
    }

    public void Dispose(string semaphoreName)
    {
        DistributedSemaphoreProvider.Dispose(semaphoreName);
    }
}

public class DistributedSemaphore<T> : DistributedSemaphore, IDistributedSemaphore<T>
    where T : class
{
    public DistributedSemaphore(
        IEnumerable<IDistributedSemaphoreProvider> distributedSemaphoreProviders,
        IOptions<DistributedSemaphoreOptions> optionsAccessor)
        : base(distributedSemaphoreProviders, optionsAccessor)
    {
    }

    public void Dispose()
    {
        Dispose(typeof(T).FullName);
    }

    public override void Initalization(string semaphoreValue = null, int initialCount = 1, string tenantId = null)
    {
        base.Initalization(typeof(T).FullName + (semaphoreValue == null ? "" : (":" + semaphoreValue)), initialCount, tenantId);
    }
}
